from kivy.app import App

from dryfire.model.moaModel import MOAModel
from dryfire.model.moasModel import HorizontalBarsMOAModel, \
    VerticalBarsMOAModel
from dryfire.model.targetsModel import \
    PaperPlateDryFireTargetModel, BeerCanDryFireTargetModel, \
    HeadDryFireTargetModel, HipsAndHeadDryFireTargetModel, \
    TorsoDryFireTargetModel, ISPCMetricCZoneDryFireTargetModel, \
    ISPCClassicCZoneDryFireTargetModel
from dryfire.view.targetSlide import TargetSlide
from dryfire.model.persistentData import RangesModel


class RangesViewModel(object):
    """
    The view model for the InitialView class.
    """

    min_seconds_up = 4
    max_seconds_up = 10
    seconds_up_step = 2
    min_seconds_down = 5
    max_seconds_down = 20
    seconds_down_step = 5
    min_distance_feet = 1
    options_yards = (25, 50, 75, 100)

    def __init__(self, view):
        self._view = view
        self._appdata = App.get_running_app().appdata
        self._targets = {}
        self._build_targets()
        self._slides = {}
        self._build_slides()
        self._record_ident = None
        self._record = {}
        self._is_target = True

    # view methods

    def v_initialize_view(self):
        # initialize view with ranges records
        ranges = self._appdata['ranges']
        idents = ranges.data.keys()
        for i in idents:
            record = ranges.get(i)
            self._view.vm_add_range(i, record['description']) 
        
    # step 1 : the user pressed the add a new record button
    
    def w_handle_create_button_on_press(self, instance):
        self._record_ident = None
        self._record = self._appdata['ranges'].default_record
        self.w_select_a_target()
        
    # other step 1 : the user selects a range record action
    #  to practice, edit or delete
    
    def w_handle_ranges_record(self, ident):
        # the user has selected a range record
        self._record_ident = ident
        self._record = self._appdata['ranges'].get(ident)
        self.select_a_range_record_option()

    def select_a_range_record_option(self):
        from dryfire.view.rangesView import RangesView
        # offer options for the record
        options = RangesView.commands
        self._view.select_options('Options',
                                  options,
                                  options[0],
                                  self.w_handle_ranges_record_command,
                                  help='What would you like to do with this record?')

    def w_handle_ranges_record_command(self, command):
        from dryfire.view.rangesView import RangesView
        if command == RangesView.practice_command:
            # open the practice panel
            view = self._view
            # initialize the target
            target_name = self._record['target']
            target = self._targets[target_name]['practice_target']
            # pixels per inch
            cal_ident = self._record['calibration']
            calibrations = self._appdata['calibrations']
            cal_record = calibrations.get(cal_ident)
            target.pixels_per_inch = cal_record['pixels'] 
            target.position_graphics(indoor_distance_feet=self._record['feet'],
                                     outdoor_distance_yards=self._record['yards'],
                                     view_size=self._view.size)
            # setup the view model
            self._appdata['target_vm'].target_model = target
            # up and down times
            self._appdata['target_vm'].uptime_seconds = self._record['up']
            self._appdata['target_vm'].downtime_seconds = self._record['down']
            # colors
            r_bgfg = self._appdata['bgfgs'].get(self._record['bgfg'])
            self._appdata['target_vm'].set_bg_fg(r_bgfg['bg'], r_bgfg['fg'])
            # display the target panel
            self._appdata['app_view'].show_target()
        elif command == RangesView.edit_command:
            self.w_select_a_target()
        elif command == RangesView.delete_command:
            # let the user confirm deletion
            question = 'Are you sure that you want to '\
                       'delete the range record %s?' % (self._record['description'])
            self._view.question_yes_no('Delete a Range Record.',
                                       question,
                                       self.delete_a_target,
                                       back_handler=self.select_a_range_record_option)
        else:
            self._view.warn("Unknown command.")

    # step 2 : user deletes a target            

    def delete_a_target(self, true_false):
        if true_false is True:
            ident = self._record_ident
            backup = self._appdata['ranges'].backup() 
            ok = self._appdata['ranges'].remove(ident)
            if ok:
                ok, mess = self._appdata['ranges'].write()
                if ok:
                    self._view.vm_show_range_deleted(ident)
            if not ok:
                # unable to write the file
                self._appdata['ranges'].restore(backup)
                self._view.message_warn('Unable to delete %s.' % (self._record['description']))
        else:
            self._view.message_warn('Nothing deleted.')
                
    # step 2 : user edits a record
    # select a target

    def w_select_a_target(self):
        kwargs = {}
        if self._record_ident is not None:
            kwargs['back_handler'] = self.select_a_range_record_option
        # let the user select a target
        # self._record and self._record_ident are set
        names = sorted(self._slides.keys())
        slides = []
        fts = []
        for i in names:
            slide = self._slides[i]
            ft = slide.vm_get_max_feet_for_view_size(self._view.message_carousel_size)
            fts.append(ft)
            slides.append(slide)
        min_ft = min(fts)
        for slide in slides:
            slide.vm_set_indoor_distance_feet(min_ft)
        default_name = self._record['target'] or names[0]
        self._view.select_slides('Select a Target.',
                                 slides,
                                 self._slides[default_name],
                                 self.w_handle_on_target_selected,
                                 **kwargs)

    def w_handle_on_target_selected(self, slide):
        # the user has selected a target slide from the slides
        self._record['target'] = slide.target.name
        self._is_target = not isinstance(slide.target, MOAModel)
        self.w_select_bgfg()

    # step 3a : user selects a color scheme
    def w_select_bgfg(self):
        bgfgs = self._appdata['bgfgs']
        options = bgfgs.get_descriptions() 
        if self._record['bgfg'] < 0:
            default = options[0]
            self._record['bgfg'] = bgfgs.get_ident_using_description(default)
        else:
            r = bgfgs.get(self._record['bgfg'])
            default = r['description']
        self._view.select_options('The target color scheme.',
                                  options,
                                  default,
                                  self.w_handle_on_bgfg_select,
                                  back_handler=self.w_select_a_target)

    def w_handle_on_bgfg_select(self, description):
        bgfgs = self._appdata['bgfgs']
        self._record['bgfg'] = bgfgs.get_ident_using_description(description)
        if self._is_target:
            # next is distance yards
            self.w_select_distance_yards()
        else:
            # the moa board is always at 100 yards.
            # next is distance feet
            self._record['yards'] = 100
            self.w_select_distance_feet()
                                 
    # step 3b : user selects yards

    def w_select_distance_yards(self):
        if len(self._record['target']) > 0:
            target_name = self._record['target']
            if self._record['yards'] < RangesViewModel.options_yards[0]:
                self._record['yards'] = RangesViewModel.options_yards[0]
            elif self._record['yards'] > RangesViewModel.options_yards[-1]:
                self._record['yards'] = RangesViewModel.options_yards[-1]
            default = str(self._record['yards'])
            help = 'If you were at a range shooting at this target, how far away would this target be from you?'
            self._view.select_options('The target distance in yards.',
                                      [str(i) for i in RangesViewModel.options_yards],
                                      default,
                                      self.w_handle_on_distance_yards_select,
                                      help=help,
                                      back_handler=self.w_select_bgfg)

    def w_handle_on_distance_yards_select(self, text):
        """Store the yards in the record.
        Recalc the maximum feet value for this record based on yards.
        """
        yards = int(text)
        if self._record['yards'] != yards:
            self._record['yards'] = yards
        # next is select distance feet
        self.w_select_distance_feet()

    # step 4 : user distance feet widget methods
    
    def w_select_distance_feet(self):
        # recalc max feet
        v_size = self._view.size
        name = self._record['target']
        calc_target = self._targets[name]['calc_target'] 
        calc_target.position_graphics(outdoor_distance_yards=self._record['yards'])
        max_feet = calc_target.calc_max_indoor_distance_for_view_size(v_size)
        self._targets[name]['max_feet'] = max_feet
        if self._record['feet'] > max_feet:
            self._record['feet'] = max_feet
        elif self._record['feet'] < RangesViewModel.min_distance_feet:
            self._record['feet'] = RangesViewModel.min_distance_feet
        default = str(self._record['feet'])
        if max_feet > 50:
            max_feet = 50
        last = max_feet + 1
        options = [str(i) for i in range(RangesViewModel.min_distance_feet, last)]
        help = 'Select how far your eye will be from the screen when you are in your shooting stance.'
        if self._is_target:
            back_handler = self.w_select_distance_yards
        else:
            back_handler = self.w_select_bgfg  
        self._view.select_options('Your eye\'s distance from this computer screen in feet. %i feet maximum.' % (max_feet),
                                  options,
                                  default,
                                  self.w_handle_on_distance_feet_select,
                                  help=help,
                                  back_handler=back_handler)

    def w_handle_on_distance_feet_select(self, text):
        self._record['feet'] = int(text)
        if self._is_target:
            # next is seconds up
            self.w_select_seconds_up()
        else:
            self._record['up'] = 0
            # next is calibration
            self.w_select_calibration()

    # step 5 : seconds up widget methods

    def w_select_seconds_up(self):
        # instance is the button
        if self._record['up'] < RangesViewModel.min_seconds_up:
            self._record['up'] = RangesViewModel.min_seconds_up
        elif self._record['up'] > RangesViewModel.max_seconds_up:
            self._record['up'] = RangesViewModel.max_seconds_up
        default = str(self._record['up'])
        options = [str(i) for i in range(RangesViewModel.min_seconds_up,
                                         RangesViewModel.max_seconds_up+1,
                                         RangesViewModel.seconds_up_step)]
        help = 'This is the amount of time that you need to'\
               '\n * bring your weapon up,'\
               '\n * lineup your sights,'\
               '\n * bring your sights on the target'\
               ' as you squeeze the trigger.'
        self._view.select_options('Target pop up time in seconds.',
                                  options,
                                  default,
                                  self.w_handle_on_seconds_up_select,
                                  help=help,
                                  back_handler=self.w_select_distance_feet)

    def w_handle_on_seconds_up_select(self, text):
        self._record['up'] = int(text)
        # next seconds down
        self.w_select_seconds_down()

    # step 6 : seconds down widget methods

    def w_select_seconds_down(self):
        if self._record['down'] < RangesViewModel.min_seconds_down:
            self._record['down'] = RangesViewModel.min_seconds_down
        elif self._record['down'] > RangesViewModel.max_seconds_down:
            self._record['down'] = RangesViewModel.max_seconds_down
        default = str(self._record['down'])
        help = 'You will probably want enough time to'\
               '\n * review the last shot,'\
               '\n * reload the snap cap,'\
               '\n * return to low ready and wait for the target to reappear.'
        options = [str(i) for i in range(RangesViewModel.min_seconds_down,
                                         RangesViewModel.max_seconds_down+1,
                                         RangesViewModel.seconds_down_step)]
        self._view.select_options('Target down time in seconds.',
                                  options,
                                  default,
                                  self.w_handle_on_seconds_down_select,
                                  help=help,
                                  back_handler=self.w_select_seconds_up)

    def w_handle_on_seconds_down_select(self, text):
        self._record['down'] = int(text)
        self.w_select_calibration()
        
    def w_select_calibration(self):
        calibrations = self._appdata['calibrations']
        options = calibrations.get_descriptions()
        r = calibrations.get(self._record['calibration'])
        if r is not None:
            default = r['description']
        else:
            default = options[0]
        if self._is_target:
            back_handler = self.w_select_seconds_down
        else:
            back_handler = self.w_select_distance_feet
        self._view.select_options('Calibration.',
                                  options,
                                  default,
                                  self.w_handle_on_calibration_select,
                                  back_handler=back_handler)
        
    def w_handle_on_calibration_select(self, description):
        calibrations = self._appdata['calibrations']
        ident = calibrations.get_ident_using_description(description)
        self._record['calibration'] = ident
        self.w_enter_description()

    # step 8 : describe the target and range
    
    def w_enter_description(self, **kwargs):
        kwargs['back_handler'] = self.w_select_seconds_down
        self._view.edit_line('Short description.',
                             'Enter a short description.',
                             self._record['description'],
                             self.w_handle_on_description_enter,
                             **kwargs)

    def w_handle_on_description_enter(self, description):
        description = description.strip()
        if len(description) > 0:
            ranges = self._appdata['ranges']
            ident = ranges.get_ident_using_description(description)
            if ident is not None and self._record_ident != ident:
                help = 'You are already using the description "%s"'\
                       ' with another target setup.' % (description) 
                self.w_enter_description(help=help)
            else:
                self._record['description'] = description
                self._save_record()
        else:
            help = 'You did not entered any text.'
            self.w_enter_description(help=help)

    # non public methods
    
    # non public slides methods
        
    def _build_slides(self):
        t = BeerCanDryFireTargetModel()
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
        t = PaperPlateDryFireTargetModel()
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
        t = HeadDryFireTargetModel()
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
        t = HipsAndHeadDryFireTargetModel() 
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
        t = TorsoDryFireTargetModel() 
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
        t = HorizontalBarsMOAModel() 
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
        t = VerticalBarsMOAModel() 
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
        t = ISPCMetricCZoneDryFireTargetModel()
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
        t = ISPCClassicCZoneDryFireTargetModel()
        self._slides[t.name] = TargetSlide(t,
                                           size_hint=(1.0, 1.0))
    
    # non public target methods
    
    def _target_dict(self, label, carousel_target, practice_target, calc_target, max_feet=-1):
        return {'label':label,
                'carousel_target':carousel_target,
                'practice_target':practice_target,
                'calc_target':calc_target,
                'max_feet':max_feet}

    def _build_targets(self):
        # beer can target
        bc_target = BeerCanDryFireTargetModel()
        bc_label = self._view.build_form_label(bc_target.name,
                                               bc_target.description)
        self._targets[bc_target.name] = self._target_dict(bc_label,
                                                          bc_target,
                                                          BeerCanDryFireTargetModel(),
                                                          BeerCanDryFireTargetModel())
        # paper plate target
        pp_target = PaperPlateDryFireTargetModel()
        pp_label = self._view.build_form_label(pp_target.name,
                                               pp_target.description)
        self._targets[pp_target.name] = self._target_dict(pp_label,
                                                          pp_target,
                                                          PaperPlateDryFireTargetModel(),
                                                          PaperPlateDryFireTargetModel())
        # head target
        h_target = HeadDryFireTargetModel()
        h_label = self._view.build_form_label(h_target.name,
                                              h_target.description)
        self._targets[h_target.name] = self._target_dict(h_label,
                                                         h_target,
                                                         HeadDryFireTargetModel(),
                                                         HeadDryFireTargetModel())
        # hips and head target
        hh_target = HipsAndHeadDryFireTargetModel()
        hh_label = self._view.build_form_label(hh_target.name,
                                               hh_target.description)
        self._targets[hh_target.name] = self._target_dict(hh_label,
                                                          hh_target,
                                                          HipsAndHeadDryFireTargetModel(),
                                                          HipsAndHeadDryFireTargetModel())
        # torso target
        t_target = TorsoDryFireTargetModel()
        t_label = self._view.build_form_label(t_target.name,
                                              t_target.description)
        self._targets[t_target.name] = self._target_dict(t_label,
                                                         t_target,
                                                         TorsoDryFireTargetModel(),
                                                         TorsoDryFireTargetModel())
        # horizontal bar moa board
        hbmoa_target = HorizontalBarsMOAModel()
        hbmoa_label = self._view.build_form_label(hbmoa_target.name,
                                                  hbmoa_target.description)
        self._targets[hbmoa_target.name] = self._target_dict(hbmoa_label,
                                                             hbmoa_target,
                                                             HorizontalBarsMOAModel(),
                                                             HorizontalBarsMOAModel())
        # vertical bar moa board
        vbmoa_target = VerticalBarsMOAModel()
        vbmoa_label = self._view.build_form_label(vbmoa_target.name,
                                                  vbmoa_target.description)
        self._targets[vbmoa_target.name] = self._target_dict(vbmoa_label,
                                                             vbmoa_target,
                                                             VerticalBarsMOAModel(),
                                                             VerticalBarsMOAModel())
        # ispc metric c zone target
        t_target = ISPCMetricCZoneDryFireTargetModel()
        t_label = self._view.build_form_label(t_target.name,
                                              t_target.description)
        self._targets[t_target.name] = self._target_dict(t_label,
                                                         t_target,
                                                         ISPCMetricCZoneDryFireTargetModel(),
                                                         ISPCMetricCZoneDryFireTargetModel())
        # ispc classic c zone target
        t_target = ISPCClassicCZoneDryFireTargetModel()
        t_label = self._view.build_form_label(t_target.name,
                                              t_target.description)
        self._targets[t_target.name] = self._target_dict(t_label,
                                                         t_target,
                                                         ISPCClassicCZoneDryFireTargetModel(),
                                                         ISPCClassicCZoneDryFireTargetModel())

    def _save_record(self):
        ranges = self._appdata['ranges']
        # fix the record if its not for a target
        if self._is_target is False:
            self._record['yards'] = 100
        # save the record
        backup = ranges.backup()
        if self._record_ident is None:
            ok, ident = ranges.add(self._record_ident, self._record)
            if ok:
                ok, mess = ranges.write()
            if ok:
                self._view.vm_show_range_added(ident, self._record['description'])
        else:
            ok = ranges.set(self._record_ident, self._record)
            if ok:
                ok, mess = ranges.write()
            if ok:
                self._view.vm_show_range_edited(self._record_ident, self._record['description'])
